/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lvyh.lightframe.core.provider;

import com.lvyh.lightframe.common.util.CollectionUtil;
import com.lvyh.lightframe.common.util.IpUtil;

import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ProviderInfo implements Serializable {

    private static final long serialVersionUID = -56L;

    public static final String ATTR_WARM_UP_END_TIME = "warmupEndTime";
    public static final String ATTR_WARMUP_WEIGHT = "warmupWeight";

    private transient String originUrl;

    private String protocolType = "lucky";
    private String host;

    private int port = 80;

    private String path;

    /**
     * The serialization method is specified by the server, which shall prevail
     */
    private String serializationType;

    private int rpcVersion;

    /**
     * weight
     */
    private transient volatile int weight = 100;

    /**
     * Service status
     */
    private transient volatile ProviderStatus status = ProviderStatus.AVAILABLE;

    /**
     * Static properties
     */
    private final ConcurrentMap<String, String> staticAttrs = new ConcurrentHashMap<String, String>();

    /**
     * Dynamic properties
     */
    private final transient ConcurrentMap<String, Object> dynamicAttrs = new ConcurrentHashMap<String, Object>();

    public ProviderInfo() {
    }

    public ProviderInfo(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public ProviderInfo(String host, int port, String originUrl) {
        this.host = host;
        this.port = port;
        this.originUrl = originUrl;
    }

    public ProviderInfo(String address) {
        Object[] array = IpUtil.parseIpPort(address);
        String host = (String) array[0];
        int port = (int) array[1];
        this.host = host;
        this.port = port;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ProviderInfo that = (ProviderInfo) o;

        if (port != that.port) {
            return false;
        }
        if (rpcVersion != that.rpcVersion) {
            return false;
        }
        if (protocolType != null ? !protocolType.equals(that.protocolType) : that.protocolType != null) {
            return false;
        }
        if (host != null ? !host.equals(that.host) : that.host != null) {
            return false;
        }
        if (path != null ? !path.equals(that.path) : that.path != null) {
            return false;
        }
        if (serializationType != null ? !serializationType.equals(that.serializationType)
                : that.serializationType != null) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int result = (protocolType != null ? protocolType.hashCode() : 0);
        result = 31 * result + (host != null ? host.hashCode() : 0);
        result = 31 * result + port;
        result = 31 * result + (path != null ? path.hashCode() : 0);
        result = 31 * result + (serializationType != null ? serializationType.hashCode() : 0);
        result = 31 * result + rpcVersion;
        return result;
    }

    public String getOriginUrl() {
        return originUrl;
    }

    public ProviderInfo setOriginUrl(String originUrl) {
        this.originUrl = originUrl;
        return this;
    }

    public String getProtocolType() {
        return protocolType;
    }

    public ProviderInfo setProtocolType(String protocolType) {
        this.protocolType = protocolType;
        return this;
    }

    public String getHost() {
        return host;
    }

    public ProviderInfo setHost(String host) {
        this.host = host;
        return this;
    }

    public int getPort() {
        return port;
    }

    public ProviderInfo setPort(int port) {
        this.port = port;
        return this;
    }

    public String getPath() {
        return path;
    }

    public ProviderInfo setPath(String path) {
        this.path = path;
        return this;
    }

    public String getSerializationType() {
        return serializationType;
    }

    public ProviderInfo setSerializationType(String serializationType) {
        this.serializationType = serializationType;
        return this;
    }

    public int getWeight() {
        ProviderStatus status = getStatus();
        if (status == ProviderStatus.WARMING_UP) {
            try {
                Integer warmUpWeight = (Integer) getDynamicAttr(ATTR_WARMUP_WEIGHT);
                if (warmUpWeight != null) {
                    return warmUpWeight;
                }
            } catch (Exception e) {
                return weight;
            }
        }
        return weight;
    }

    public ProviderInfo setWeight(int weight) {
        this.weight = weight;
        return this;
    }

    public int getRpcVersion() {
        return rpcVersion;
    }

    public ProviderInfo setRpcVersion(int rpcVersion) {
        this.rpcVersion = rpcVersion;
        return this;
    }

    public ProviderStatus getStatus() {
        if (status == ProviderStatus.WARMING_UP) {
            Object dynamicAttr = getDynamicAttr(ATTR_WARM_UP_END_TIME);
            if (dynamicAttr != null && System.currentTimeMillis() > (Long) dynamicAttr) {
                status = ProviderStatus.AVAILABLE;
                setDynamicAttr(ATTR_WARM_UP_END_TIME, null);
            }
        }
        return status;
    }

    public ProviderInfo setStatus(ProviderStatus status) {
        this.status = status;
        return this;
    }

    /**
     * Gets static attribute.
     *
     * @return the static attribute
     */
    public ConcurrentMap<String, String> getStaticAttrs() {
        return staticAttrs;
    }

    public ProviderInfo setStaticAttrs(Map<String, String> staticAttrs) {
        this.staticAttrs.clear();
        if (CollectionUtil.isNotEmpty(staticAttrs)) {
            this.staticAttrs.putAll(staticAttrs);
        }
        return this;
    }

    public ConcurrentMap<String, Object> getDynamicAttrs() {
        return dynamicAttrs;
    }

    public ProviderInfo setDynamicAttrs(Map<String, Object> dynamicAttrs) {
        this.dynamicAttrs.clear();
        this.dynamicAttrs.putAll(dynamicAttrs);
        return this;
    }

    public String getStaticAttr(String staticAttrKey) {
        return staticAttrs.get(staticAttrKey);
    }

    public ProviderInfo setStaticAttr(String staticAttrKey, String staticAttrValue) {
        if (staticAttrValue == null) {
            staticAttrs.remove(staticAttrKey);
        } else {
            staticAttrs.put(staticAttrKey, staticAttrValue);
        }
        return this;
    }

    public Object getDynamicAttr(String dynamicAttrKey) {
        return dynamicAttrs.get(dynamicAttrKey);
    }

    public ProviderInfo setDynamicAttr(String dynamicAttrKey, Object dynamicAttrValue) {
        if (dynamicAttrValue == null) {
            dynamicAttrs.remove(dynamicAttrKey);
        } else {
            dynamicAttrs.put(dynamicAttrKey, dynamicAttrValue);
        }
        return this;
    }

    @Override
    public String toString() {
        return originUrl == null ? host + ":" + port : originUrl;
    }

    /**
     * Get the attribute value, first take the dynamic attribute, then take the static attribute
     */
    public String getAttr(String key) {
        String val = (String) dynamicAttrs.get(key);
        return val == null ? staticAttrs.get(key) : val;
    }
}
